/*
    multiboot.c
    Multiboot code for setting up an environment for the kernel and booting it from the kernel image.
    environment:
    boot_info *bi is passed to entry point
    kernel image is mapped to kernel->loadaddr
    kernel bss is mapped and wiped
    *temporary stack*
    page tables and directory are mapped to 0xFFC00000-0xFFFFF000
    memory map, boot_info are mapped
    initrd is mapped to 0xF0000000
    **ugh I'll finish this later, stub is mapped too...
*/


#include "multiboot.h"
#include "video.h"
#include "misc.h"
#include "kernel_image.h"
#include "io.h"

// the address and size of our kernel image
extern void* kernel_addr;
extern unsigned long kernel_size;
extern char ebss[]; // the length of what was loaded

//
int quiet = 0;

// boot information structure passed to the kernel
boot_info bi;

// address of the page directory
unsigned long* pgdir;
// the next free address
void* maxaddr;

// This function may be called once the boot information structure has been populated.
void* newpage()
{
    void* t = maxaddr+0x1000;
    maxaddr = t + 0x1000;
    return maxaddr;
}

int cmdeq(const char *cmdline, const char *ref)
{
    while (*cmdline == *ref && *cmdline && *ref && *cmdline != '\x20') cmdline++, ref++;
    return (*cmdline == *ref) || (*cmdline == '\x20' && !*ref);
}

void error(const char* msg)
{
    printf("Error: %s\n", msg);
    freeze();
}

// message -- conditional function pointer used with quiet command line parameter
void (*message)(const char*, ...) = printf;

void nullmsg(const char* format, ...) {}

void multiboot(multiboot_info_t* mbi, unsigned int magic)
{
    // initialize our video functions.
    init_video();
    // We can't boot from this function, we weren't booted from a multiboot-compliant bootloader.
    if (magic != MULTIBOOT_BOOTLOADER_MAGIC)
    {
        error("Invalid multiboot magic number");
        return;
    }
    // command line
    if (CHECK_FLAG (mbi->flags, 2))
    {
        char* f = (char*)mbi->cmdline;
        while (*f)
        {
            if (cmdeq(f, "quiet")) quiet = 1;
            while (*f && *f != '\x20') f++;
            while (*f == '\x20') f++;
        }
        bi.cmdline = (char*)mbi->cmdline;
    }
    // turn off message printing if the 'quiet' parameter is passed to the kernel:
    if (quiet) message = nullmsg; // message is a function pointer, printf prints a message, nullmsg does nothing.
    // print some various things:
    if (CHECK_FLAG (mbi->flags, 9))
        message("multiboot bootloader: %s\n", *(const char**)((void*)mbi+64));
    message("myos command line: %s\n", bi.cmdline);
    // we required that the bootloader provide memory information.
    bi.mem_lower = mbi->mem_lower;
    bi.mem_upper = mbi->mem_upper;
    message("lower memory: %dKB; upper memory: %dKB\n", bi.mem_lower, bi.mem_upper);
    // there must be a module loaded -- our initrd image.
    if (!CHECK_FLAG (mbi->flags, 3) || mbi->mods_count != 1)
    {
        message("No initrd module loaded.");
    }
    else
    {
        module_t *mod = (void*)mbi->mods_addr;
        bi.initrd_addr = (void*)mod->mod_start;
        bi.initrd_length = (void*)mod->mod_end-mod->mod_start;
    }
    // create a memory map
    bi.mmap_size = sizeof(memory_map);
    if (CHECK_FLAG (mbi->flags, 6))
    {
        // transform from memory_map_t[] array to memory_map[] array
        memory_map_t *mmap;
	memory_map   *m;
        bi.mmap_addr = (void*)mbi->mmap_addr;
        int c = 0;
        message("Map of free memory:\n");
        int s;
        for (mmap = (memory_map_t *) mbi->mmap_addr, m = (memory_map*) mbi->mmap_addr;
                (unsigned long) mmap < mbi->mmap_addr + mbi->mmap_length;
                mmap = (memory_map_t *) ((unsigned long) mmap
                                         + s + sizeof (mmap->size)), m++, c++)
        {
            s = mmap->size;
            *m = *(memory_map*)((void*)mmap+sizeof(mmap->size));
            if (m->type == 1) message("  %u (length: %u)\n", m->base_addr_low, m->base_addr_high, m->length_low, m->length_high);
        }
        bi.mmap_length = c * sizeof (memory_map);
    }
    // Determine the farthest address used:
    maxaddr = (void*)0x100000;
    if ((unsigned)ebss > (unsigned)addr)
        maxaddr = ebbs;
    if ((unsigned)bi.mmap_addr + bi.mmap_length > (unsigned)maxaddr)
        maxaddr = (void*)bi.mmap_addr + bi.mmap_length;
    if ((unsigned)bi.initrd_addr + bi.initrd_length > (unsigned)maxaddr)
        maxaddr = (void*)bi.initrd_addr + bi.initrd_length;
    maxaddr = (void*)(((unsigned)maxaddr+0xFFF)&~0xFFF); 

    // start paging

    // create a new page directory
    pgdir = newpage();
    // wipe pgdir
    int i;
    for (i = 0; i < 1023; i++)
        pgdir[i] = 0;
    // map page directory into itself, making 0xFFC00000-0xFFFFF000 the page tables,
    //   and 0xFFFFF000 the page directory.
    pgdir[1023] = (unsigned)pgdir|PF_WRITE|PF_PRESENT;
    
    // start mapping:
    // kernel image:
    kernel_image *kernel = kernel_addr;
    
    /*
      Code to eventually run after all paging is setup:
    // start the kernel
    kernel->entry(&bi); // call the entry function pointer in the kernel image header, passing the address of the boot information structure.
    */
}
